package com.sspro.simplertmp.core;

import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.support.annotation.NonNull;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.sspro.simplertmp.core.encode.AudioEncodeFun;
import com.sspro.simplertmp.core.encode.EncodeCallbackListener;
import com.sspro.simplertmp.core.encode.VideoEncodeFun;
import com.sspro.simplertmp.core.utils.Constant;
import com.sspro.simplertmp.core.utils.KitUtil;
import java.io.IOException;
import java.util.List;

public class PushClient implements SurfaceHolder.Callback, Camera.PreviewCallback, EncodeCallbackListener {

    private Context mContext;
    private int mCameraIsPreview;
    private boolean mIsRecording;
    private Camera mCamera = null;
    private SurfaceView mSurfaceView;
    private AudioProfile mAudioProfile;
    private VideoProfile mVideoProfile;
    private BasicProfile mBasicProfile;
    private AudioEncodeFun mAudioEncodeFun = null;
    private VideoEncodeFun mVideoEncodeFun = null;
    private ErrorHandleListener mErrorHandleListener;
    private DataCallbackListener mDataCallbackListener;

    public PushClient() {}

    public PushClient(SurfaceView surfaceView) {
        mSurfaceView = surfaceView;
    }

    public PushClient(
            Context context,
            SurfaceView surfaceView,
            AudioProfile audioProfile,
            VideoProfile videoProfile,
            BasicProfile basicProfile,
            ErrorHandleListener errorHandleListener,
            DataCallbackListener dataCallbackListener) {

        mContext = context;
        mSurfaceView = surfaceView;
        mAudioProfile = audioProfile;
        mVideoProfile = videoProfile;
        mBasicProfile = basicProfile;
        mErrorHandleListener = errorHandleListener;
        mDataCallbackListener = dataCallbackListener;
    }

    public void setAudioProfile(AudioProfile audioProfile) {
        mAudioProfile = audioProfile;
    }

    public void setVideoProfile(VideoProfile videoProfile) {
        mVideoProfile = videoProfile;
    }

    public void setBasicProfile(BasicProfile basicProfile) {
        mBasicProfile = basicProfile;
    }

    public void setErrorHandleListener(ErrorHandleListener listener) {
        mErrorHandleListener = listener;
    }

    public void setDataCallbackListener(DataCallbackListener listener) {
        mDataCallbackListener = listener;
    }

    public void init() {
        mAudioEncodeFun = new AudioEncodeFun(mContext, mAudioProfile, mBasicProfile, this);
        mVideoEncodeFun = new VideoEncodeFun(mVideoProfile.preWidth, mVideoProfile.preHeight, this);
        setHolder();
    }

    public void startPreview(SurfaceHolder holder) {
        if (mCamera == null) return;
        setCameraParams();
        try {
            if (holder != null) {
                mCamera.setPreviewDisplay(holder);
            } else {
                SurfaceTexture surfaceTexture = new SurfaceTexture(10);
                mCamera.setPreviewTexture(surfaceTexture);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        mCamera.setPreviewCallback(this);
        mCamera.startPreview();
        mCamera.cancelAutoFocus();
        mCameraIsPreview = Constant.CameraStatus.OPEN;
        mDataCallbackListener.onCameraStatusCallback(mCameraIsPreview);
    }

    public void restartPreview(SurfaceHolder holder) {
        if(holder != null) {
            stopPreview();
            closeCamera();
            openCamera();
            startPreview(holder);
        } else {
            mErrorHandleListener.onRenderError(Constant.RenderErrorCode.HOLDER_NOT_NULL);
        }
    }

    public void stopPreview() {
        if (mCameraIsPreview == 0) return;
        if (mCamera != null) {
            mCamera.setPreviewCallbackWithBuffer(null);
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            try {
                mCamera.setPreviewDisplay(null);
                mCamera.setPreviewTexture(null);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        mCameraIsPreview = Constant.CameraStatus.CLOSE;
        mDataCallbackListener.onCameraStatusCallback(mCameraIsPreview);
    }

    public void startRecord() {
        mAudioEncodeFun.startRecord();
    }

    public void stopRecord() {
        mAudioEncodeFun.stopRecord();
    }

    private void setHolder() {
        SurfaceHolder holder = mSurfaceView.getHolder();
        holder.addCallback(this);
    }

    private void setCameraParams() {
        Camera.Parameters params = mCamera.getParameters();
        List<Camera.Size> supportedPreviewSizes = params.getSupportedPreviewSizes();
        List<Camera.Size> supportedPictureSizes = params.getSupportedPictureSizes();
        mDataCallbackListener.onGetCameraPreviewSizes(supportedPreviewSizes);
        mDataCallbackListener.onGetCameraPictureSizes(supportedPictureSizes);
        List<String> modes = params.getSupportedFocusModes();
        if (modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO))
            params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        else if (modes.contains(Camera.Parameters.FOCUS_MODE_AUTO))
            params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        params.setPreviewFormat(ImageFormat.NV21);
        params.setPreviewFrameRate(mVideoProfile.fps);
        List<int[]> ranges = params.getSupportedPreviewFpsRange();
        int[] f = setPreviewFpsRanges(ranges);
        if (f != null) params.setPreviewFpsRange(f[0], f[1]);
        params.setPreviewSize(mVideoProfile.preWidth, mVideoProfile.preHeight);
        mCamera.setParameters(params);
    }

    private void openCamera() {
        if (mVideoProfile.cameraId == Constant.CameraType.BACK)
            openBackCamera();
        else
            openFrontCamera();
    }

    private void closeCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.release();
            mCamera = null;
        }
    }

    private void openBackCamera() {
        if (mCamera != null)
            reconnectCamera();
        else
            createCamera();
    }

    private void openFrontCamera() {
        if (mCamera != null)
            reconnectCamera();
        else
            setOpenCameraId();
    }

    private void createCamera() {
        for (int j = 0; j < 3; j++) {
            try {
                mCamera = Camera.open();
            } catch (Exception e) {
                e.printStackTrace();
                mCamera = null;
                if (j == 2)
                    break;
                KitUtil.TSleep(5000);
                continue;
            }
            break;
        }
    }

    private void reconnectCamera() {
        try {
            mCamera.reconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void setOpenCameraId() {
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        int i, n = android.hardware.Camera.getNumberOfCameras();
        for (i = 0; i < n; i++) {
            Camera.getCameraInfo(i, cameraInfo);
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                try {
                    mCamera = Camera.open(i);
                } catch (Exception e) {
                    e.printStackTrace();
                    mCamera = null;
                    if (i == n - 1)
                        break;
                    KitUtil.TSleep(5000);
                    continue;
                }
                break;
            }
        }
    }

    private int[] setPreviewFpsRanges(List<int[]> ranges) {
        int fps1000 = mVideoProfile.fps * 1000;
        int[] f = null;
        for (int[] r : ranges) {
            if (fps1000 >= r[0] && fps1000 <= r[1]) {
                if (f == null || r[0] > f[0] || r[1] > f[1])
                    f = r;
            }
        }
        return f;
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {

    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
        restartPreview(holder);
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        mVideoEncodeFun.encodeFrame(data, mIsRecording);
    }

    @Override
    public void onVideoEncodeStatusCallback(int errCode) {
        mErrorHandleListener.onEncodeError(errCode);
        mDataCallbackListener.onRecorderStatusCallback(false);
        mAudioEncodeFun.stopRecord();
    }

    @Override
    public void onAudioEncodeStatusCallback(boolean isRecording) {
        mIsRecording = isRecording;
        Constant.isRecording = mIsRecording;
    }
}
